// ##########################################################################
// #                                                                        #
// #                              CLOUDCOMPARE                              #
// #                                                                        #
// #  This program is free software; you can redistribute it and/or modify  #
// #  it under the terms of the GNU General Public License as published by  #
// #  the Free Software Foundation; version 2 or later of the License.      #
// #                                                                        #
// #  This program is distributed in the hope that it will be useful,       #
// #  but WITHOUT ANY WARRANTY; without even the implied warranty of        #
// #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the          #
// #  GNU General Public License for more details.                          #
// #                                                                        #
// #          COPYRIGHT: CloudCompare project                               #
// #                                                                        #
// ##########################################################################

#include "ccPluginInfoDlg.h"

#include "ccPluginManager.h"
#include "ccStdPluginInterface.h"
#include "ui_ccPluginInfoDlg.h"

#include <QDebug>
#include <QDir>
#include <QSortFilterProxyModel>
#include <QStandardItemModel>

static QString sFormatReferenceList(const ccPluginInterface::ReferenceList& list)
{
	const QString linkFormat(" <a href=\"%1\" style=\"text-decoration:none\">&#x1F517;</a>");
	QString       formattedText;
	int           referenceNum = 1;

	for (const ccPluginInterface::Reference& reference : list)
	{
		formattedText += QStringLiteral("%1. ").arg(QString::number(referenceNum++));

		formattedText += reference.article;

		if (!reference.url.isEmpty())
		{
			formattedText += linkFormat.arg(reference.url);
		}

		formattedText += QStringLiteral("<br/>");
	}

	return formattedText;
}

static QString sFormatContactList(const ccPluginInterface::ContactList& list, const QString& pluginName)
{
	const QString emailFormat("&lt;<a href=\"mailto:%1?Subject=CloudCompare %2\">%1</a>&gt;");
	QString       formattedText;

	for (const ccPluginInterface::Contact& contact : list)
	{
		formattedText += contact.name;

		if (!contact.email.isEmpty())
		{
			formattedText += emailFormat.arg(contact.email, pluginName);
		}

		formattedText += QStringLiteral("<br/>");
	}

	return formattedText;
}

namespace
{
	class _Icons
	{
	  public:
		static QIcon sGetIcon(CC_PLUGIN_TYPE inPluginType)
		{
			if (sIconMap.empty())
			{
				_init();
			}

			return sIconMap[inPluginType];
		}

	  private:
		static void _init()
		{
			if (!sIconMap.empty())
			{
				return;
			}

			sIconMap[CC_STD_PLUGIN]       = QIcon(":/CC/pluginManager/images/std_plugin.png");
			sIconMap[CC_GL_FILTER_PLUGIN] = QIcon(":/CC/pluginManager/images/gl_plugin.png");
			sIconMap[CC_IO_FILTER_PLUGIN] = QIcon(":/CC/pluginManager/images/io_plugin.png");
		}

		static QMap<CC_PLUGIN_TYPE, QIcon> sIconMap;
	};

	QMap<CC_PLUGIN_TYPE, QIcon> _Icons::sIconMap;
} // namespace

ccPluginInfoDlg::ccPluginInfoDlg(QWidget* parent)
    : QDialog(parent)
    , m_UI(new Ui::ccPluginInfoDlg)
    , m_ProxyModel(new QSortFilterProxyModel(this))
    , m_ItemModel(new QStandardItemModel(this))
{
	m_UI->setupUi(this);

	setWindowTitle(tr("About Plugins"));

	m_UI->mWarningLabel->setText(tr("Enabling/disabling plugins will take effect next time you run %1").arg(QApplication::applicationName()));
	m_UI->mWarningLabel->setStyleSheet(QStringLiteral("QLabel { background-color : #FFFF99; color : black; }"));
	m_UI->mWarningLabel->hide();

	m_UI->mSearchLineEdit->setStyleSheet("QLineEdit, QLineEdit:focus { border: none; }");
	m_UI->mSearchLineEdit->setAttribute(Qt::WA_MacShowFocusRect, false);

	m_ProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
	m_ProxyModel->setSourceModel(m_ItemModel);

	m_UI->mPluginListView->setModel(m_ProxyModel);
	m_UI->mPluginListView->setFocus();

	connect(m_UI->mSearchLineEdit, &QLineEdit::textEdited, m_ProxyModel, &QSortFilterProxyModel::setFilterFixedString);

	connect(m_UI->mPluginListView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ccPluginInfoDlg::selectionChanged);
}

ccPluginInfoDlg::~ccPluginInfoDlg()
{
	delete m_UI;
}

void ccPluginInfoDlg::setPluginPaths(const QStringList& pluginPaths)
{
	QString paths;

	for (const QString& path : pluginPaths)
	{
		paths += QDir::toNativeSeparators(path);
		paths += QStringLiteral("\n");
	}

	m_UI->mPluginPathTextEdit->setText(paths);
}

void ccPluginInfoDlg::setPluginList(const QList<ccPluginInterface*>& pluginList)
{
	m_ItemModel->clear();
	m_ItemModel->setRowCount(pluginList.count());
	m_ItemModel->setColumnCount(1);

	int row = 0;

	for (const ccPluginInterface* plugin : pluginList)
	{
		auto name    = plugin->getName();
		auto tooltip = tr("%1 Plugin").arg(plugin->getName());

		if (plugin->isCore())
		{
			tooltip += tr(" (core)");
		}
		else
		{
			name += " 👽";
			tooltip += tr(" (3rd Party)");
		}

		QStandardItem* item = new QStandardItem(name);

		item->setCheckable(true);

		if (ccPluginManager::Get().isEnabled(plugin))
		{
			item->setCheckState(Qt::Checked);
		}

		item->setData(QVariant::fromValue(plugin), PLUGIN_PTR);
		item->setIcon(_Icons::sGetIcon(plugin->getType()));
		item->setToolTip(tooltip);

		m_ItemModel->setItem(row, 0, item);

		++row;
	}

	if (!pluginList.empty())
	{
		m_ItemModel->sort(0);

		QModelIndex index = m_ItemModel->index(0, 0);

		m_UI->mPluginListView->setCurrentIndex(index);
	}

	connect(m_ItemModel, &QStandardItemModel::itemChanged, this, &ccPluginInfoDlg::itemChanged);
}

const ccPluginInterface* ccPluginInfoDlg::pluginFromItemData(const QStandardItem* item) const
{
	return item->data(PLUGIN_PTR).value<const ccPluginInterface*>();
	;
}

void ccPluginInfoDlg::selectionChanged(const QModelIndex& current, const QModelIndex& previous)
{
	Q_UNUSED(previous);

	auto sourceItem = m_ProxyModel->mapToSource(current);
	auto item       = m_ItemModel->itemFromIndex(sourceItem);

	if (item == nullptr)
	{
		// This happens if we are filtering and there are no results
		updatePluginInfo(nullptr);
		return;
	}

	auto plugin = pluginFromItemData(item);

	updatePluginInfo(plugin);
}

void ccPluginInfoDlg::itemChanged(QStandardItem* item)
{
	bool checked = item->checkState() == Qt::Checked;
	auto plugin  = pluginFromItemData(item);

	if (plugin != nullptr)
	{
		ccPluginManager::Get().setPluginEnabled(plugin, checked);

		if (m_UI->mWarningLabel->isHidden())
		{
			ccLog::Warning(m_UI->mWarningLabel->text());

			m_UI->mWarningLabel->show();
		}
	}
}

void ccPluginInfoDlg::updatePluginInfo(const ccPluginInterface* plugin)
{
	if (plugin == nullptr)
	{
		m_UI->mIcon->setPixmap(QPixmap());
		m_UI->mNameLabel->setText(tr("(No plugin selected)"));
		m_UI->mDescriptionTextEdit->clear();
		m_UI->mReferencesTextBrowser->clear();
		m_UI->mAuthorsTextBrowser->clear();
		m_UI->mMaintainerTextBrowser->clear();
		return;
	}

	const QSize iconSize(64, 64);

	QPixmap iconPixmap;

	if (!plugin->getIcon().isNull())
	{
		iconPixmap = plugin->getIcon().pixmap(iconSize);
	}

	switch (plugin->getType())
	{
	case CC_STD_PLUGIN:
	{
		if (iconPixmap.isNull())
		{
			iconPixmap = QPixmap(":/CC/pluginManager/images/std_plugin.png").scaled(iconSize);
		}

		m_UI->mPluginTypeLabel->clear();
		break;
	}

	case CC_GL_FILTER_PLUGIN:
	{
		if (iconPixmap.isNull())
		{
			iconPixmap = QPixmap(":/CC/pluginManager/images/gl_plugin.png").scaled(iconSize);
		}

		m_UI->mPluginTypeLabel->setText(tr("GL Shader"));
		break;
	}

	case CC_IO_FILTER_PLUGIN:
	{
		if (iconPixmap.isNull())
		{
			iconPixmap = QPixmap(":/CC/pluginManager/images/io_plugin.png").scaled(iconSize);
		}

		m_UI->mPluginTypeLabel->setText(tr("I/O"));
		break;
	}
	}

	m_UI->mIcon->setPixmap(iconPixmap);

	m_UI->mNameLabel->setText(plugin->getName());
	m_UI->mDescriptionTextEdit->setHtml(plugin->getDescription());

	const QString referenceText = sFormatReferenceList(plugin->getReferences());

	if (!referenceText.isEmpty())
	{
		m_UI->mReferencesTextBrowser->setHtml(referenceText);
		m_UI->mReferencesLabel->show();
		m_UI->mReferencesTextBrowser->show();
	}
	else
	{
		m_UI->mReferencesLabel->hide();
		m_UI->mReferencesTextBrowser->hide();
		m_UI->mReferencesTextBrowser->clear();
	}

	const QString authorsText = sFormatContactList(plugin->getAuthors(), plugin->getName());

	if (!authorsText.isEmpty())
	{
		m_UI->mAuthorsTextBrowser->setHtml(authorsText);
	}
	else
	{
		m_UI->mAuthorsTextBrowser->clear();
	}

	const QString maintainersText = sFormatContactList(plugin->getMaintainers(), plugin->getName());

	if (!maintainersText.isEmpty())
	{
		m_UI->mMaintainerTextBrowser->setHtml(maintainersText);
	}
	else
	{
		m_UI->mMaintainerTextBrowser->clear();
	}
}
